package deepr.src.pg;

import deepr.exception.DeeprException;
import deepr.filter.DbFilterBehavior;
import deepr.core.*;
import deepr.filter.FilterParser;
import deepr.filter.FilterRelay;
import deepr.lib.FilterLib;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class PgFilter extends DbFilterBehavior {


  public StringBuilder like(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    Assert.isTrue(value instanceof String, property + " like value must be string");
    parser.getFilterParams().put(paramKey, "%" + value + "%");
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append(property).append(" LIKE #{").append(paramKey).append("}");
  }

  @Override
  public StringBuilder between(FilterParser parser, String object, String key, String paramKey, Object value) throws DeeprException {
    Object[] v = parser.betweenActionValueValid(object, value);
    Object min = v[0];
    Object max = v[1];

    String key2 = parser.getParamKey(object + "_" + paramKey);
    String key3 = parser.getParamKey(object + "_" + paramKey);
    parser.getFilterParams().put(key2, min);
    parser.getFilterParams().put(key3, max);
    key = FilterLib.convertKey(parser, key);
    return new StringBuilder().append(key).append(" BETWEEN #{").append(key2).append("} AND #{").append(key3).append("}");
  }

  @Override
  public StringBuilder restAction(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    parser.getFilterParams().put(paramKey, value);
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append(property).append(parser.getAction()).append("#{").append(paramKey).append("}");
  }

  @Override
  public StringBuilder jsonKey(FilterParser parser, String object, PropertyData propertyData, String property, String[] keys, Object value) throws Exception {
    keys = FilterLib.concatJsonKey(keys);
    String paramKey = keys[1];
    String eleKey = keys[0];
    String action = parser.getAction();
    if (action.equals("like")) {
      return like(parser, object, eleKey, paramKey, value);
    }
    value = parser.handleJsonKeyValue(propertyData, eleKey, value);
    if (parser.getHandlerJsonKey() != null) {
      eleKey = parser.getHandlerJsonKey();
      parser.setHandlerJsonKey(null);
    }
    if (action.equals("between")) {
      return between(parser, object, eleKey, paramKey, value);
    }
    boolean valueIsArray = value.getClass().isArray();
    if (valueIsArray) {
      return valueArray(parser, object, eleKey, paramKey, value);
    }
    return restAction(parser, object, eleKey, paramKey, value);
  }

  @Override
  public StringBuilder jsonAt(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    parser.getFilterParams().put(paramKey, value);
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append(property).append("@>#{").append(paramKey).append("}");
  }


  @Override
  public StringBuilder valueArray(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    parser.getFilterParams().put(paramKey, value);
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append(property).append("=any(#{").append(paramKey).append("})");
  }

  @Override
  public StringBuilder propertyArray(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    parser.getFilterParams().put(paramKey, value);
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append("#{").append(paramKey).append("}=any(").append(property).append(")");
  }

  @Override
  public StringBuilder twoArray(FilterParser parser, String object, String property, String paramKey, Object value) throws DeeprException {
    parser.getFilterParams().put(paramKey, value);
    property = FilterLib.convertKey(parser, property);
    return new StringBuilder().append(property).append(" && #{").append(paramKey).append("}");
  }

  public void appendToParent(FilterParser parser, Integer depth, String parent, PropertyData propertyData, StringBuilder sb) {
    String forProperty = propertyData.getForProperty();
    HashMap<String, FilterRelay> objMap = getDepthObjectFilter().getOrDefault(depth, new HashMap<>());
    FilterRelay foreign = objMap.get(parent);
    foreign.appendProperty(forProperty, sb, parser.getJoiner());
    objMap.put(parent, foreign);
    getDepthObjectFilter().put(depth, objMap);
  }

  public StringBuilder getJsonArrayFilter(FilterParser parser, FilterRelay filterRelay) {
    HashMap<String, StringBuilder> jsonArrayFilter = filterRelay.getJsonArrayFilter();
    if (jsonArrayFilter.size() == 0)
      return new StringBuilder();
    StringBuilder from = new StringBuilder();
    StringBuilder sb2 = new StringBuilder();
    for (Map.Entry<String, StringBuilder> foreignEntry : jsonArrayFilter.entrySet()) {
      String fJK = foreignEntry.getKey();
      FilterLib.appendJoiner(from, ",");
      from.append("JSONB_ARRAY_ELEMENTS(").append(fJK).append(") AS ").append(fJK).append("_ele");
      StringBuilder fJKSb = foreignEntry.getValue();
      FilterLib.appendJoiner(sb2, parser.getJoiner());
      sb2.append(fJKSb);
    }
    return new StringBuilder().append("EXISTS(SELECT 1 FROM ").append(from).
        append(" WHERE ").append(sb2).append(" LIMIT 1)");
  }

  @Override
  public StringBuilder getForeignFilter(FilterParser parser) throws DeeprException {
    HashMap<Integer, HashMap<String, FilterRelay>> depthForeign = getDepthObjectFilter();
    int depth = depthForeign.size();
    log.debug("filterMap: {}", depthForeign);
    for (int i = depth; i > 0; i--) {
      HashMap<String, FilterRelay> objectFilter = depthForeign.getOrDefault(i, new HashMap<>());
      for (Map.Entry<String, FilterRelay> entry : objectFilter.entrySet()) {
        String object = entry.getKey();
        FilterRelay filterRelay = entry.getValue();
        StringBuilder sb = getObjectFilter(parser, object, filterRelay);
        if (i == 1)
          return sb;
        String parent = filterRelay.getParent();
        SchemaData parentSchema = SchemaManager.getSchemaData(parent);
        PropertyData me = parentSchema.getPropertyData(object);
        appendToParent(parser, i - 1, parent, me, sb);
      }
    }
    return new StringBuilder();
  }

  @Override
  public StringBuilder getObjectFilter(FilterParser parser, String object, FilterRelay filterRelay) throws DeeprException {
    String joiner = parser.getJoiner();
    StringBuilder sb1 = super.getPropertyFilter(parser, filterRelay);
    StringBuilder sb2 = getJsonArrayFilter(parser, filterRelay);
    StringBuilder sb = new StringBuilder();
    if (sb1.length() > 0 && sb2.length() > 0) {
      sb1.append(joiner).append(sb2);
    } else if (sb2.length() > 0)
      sb1.append(sb2);
    String parent = filterRelay.getParent();
    if (parent == null)
      return sb1;
    log.debug("parent: {}, object: {}", parent, object);
    SchemaData parentSchema = SchemaManager.getSchemaData(parent);
    PropertyData me = parentSchema.getPropertyData(object);
    String forProperty = me.getForProperty();
    SchemaData meSchema = SchemaManager.getSchemaData(object);
    String myName = meSchema.getName();
    String primaryKey = meSchema.getPrimaryKey();
    sb.append(forProperty).append("=ANY(SELECT ").append(primaryKey).append(" FROM ").append(myName).append(" WHERE ").append(sb1).append(")");
    return sb;
  }

}
